import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
import numpy
import fileloader
import pyperclip as pc

import modules
import my_serial

root = tk.Tk()
root.title("Servo Controller")
root.geometry("1960x1080")

servo_scales = []

action_list_box:tk.Listbox
port_select_combobox:ttk.Combobox

# 开始发送记录值
start_send_flag = tk.IntVar()
action_display_text: tk.StringVar
current_action_key_time: tk.IntVar
play_action_info:tk.StringVar
is_stop_action_key_display = True


# 串口列表
serial_port_list = []
# 关键帧列表
action_key_list = []

# UI中舵机角度列表
ui_servo_angles = []

current_select:int = None

def button_message(e):
    print(e.widget.cget("text"))
    global action_key_list
    global current_select

    match e.widget.cget("text"):

        case "刷新串口":
            global serial_port_list
            serial_port_list = my_serial.get_port_list()
            port_select_combobox.config(values=serial_port_list)

        case "打开串口":
            string = port_select_combobox.get()
            if string == "":
                root.after(0, lambda: messagebox.showerror("错误", "串口不能为空"))
                return
            
            parts = string.split(" ")
            print(parts[0])
            root.after(0, lambda: my_serial.open_port(parts[0]))

        case "保存动作帧":
            if current_select is None:
                root.after(0, lambda: messagebox.showerror("错误", "请先选择一个动作帧"))
                return
            
            action_key_list[current_select].set_angles(get_servo_angles())
            action_key_list[current_select].time = current_action_key_time.get()
            return

        case "插入动作":
            if current_select is None:
                root.after(0, lambda: messagebox.showerror("错误", "请先选择一个动作帧"))
                return
            
            new_action = modules.action_key(ui_servo_angles, current_action_key_time.get())
            action_key_list.insert(current_select, new_action)
            update_action_key_listbox(action_key_list)
            return
        
        case "删除动作":
            if current_select is None:
                root.after(0, lambda: messagebox.showerror("错误", "请先选择一个动作帧"))
                return
            
            if(action_key_list.__len__() == 1):
                root.after(0, lambda: messagebox.showerror("错误", "列表中至少存在一个动作帧"))
                return
            
            
            action_key_list.pop(current_select)
            update_action_key_listbox(action_key_list)
            current_select = None
            action_display_text.set("当前选中目标帧:NULL")

            return
        
        case "复制到剪贴板":
            # 格式化角度信息
            rows = len(action_key_list)
            cols = len(action_key_list[0].angles)
            c_style_array = "int angles[%d][%d] = {" % (rows, cols)

            for i, obj in enumerate(action_key_list):
                angles = obj.angles
                angles = list(map(int, angles))

                c_style_array += "{" + ", ".join(map(str, angles)) + "}"
                if i != rows - 1:
                    c_style_array += ","
                c_style_array += "\n"

            c_style_array += "};\n"

            #格式化时间信息
            time_array = []
            for each in action_key_list:
                time_array.append(each.time)

            c_style_array += "int time[%d] = {" % (rows)
            c_style_array += ", ".join(map(str, time_array))
            c_style_array += "};"

            pc.copy(c_style_array)
            

def send_serial_data():
    data = [0xFF, 0xFF]
    for angle in ui_servo_angles:
        data.append(int(angle * 250 /270 + 0.5))

    data += [0xFE, 0xFE]

    my_serial.serial_write(data)

def update_action_key_listbox(action_keys:list):
    action_list_box.delete(0, tk.END)
    for index, _ in enumerate(action_keys):
        action_list_box.insert(index, str(index + 1))
def action_key_select(e):
    global current_select
    try:
        new_select = e.widget.curselection()[0]

        if new_select == None and current_select is not None:
            return
    except:
        return

    current_select = new_select
    print(current_select)
    if current_select is not None:
        action_display_text.set(f"当前选中目标帧:{current_select + 1}")
    else:
        action_display_text.set("当前选中目标帧:NULL")
        return

    # 更新舵机角度
    servo_angles = action_key_list[current_select].angles
    update_servo_angles(servo_angles)
    current_action_key_time.set(action_key_list[current_select].time)

def get_servo_angles():
    angles = []
    for scale in servo_scales:
        angles.append(scale.get())
    return angles

# 更新当前舵机角度
def update_servo_angles(angles):
    for i in range(len(angles)):
        servo_scales[i].set(angles[i])

# 对舵机列表进行线性插值
def linear_interpolation(index:int, num):
    # 将num转换为int
    try:
        num = int(num)
    except:
        root.after(0, lambda: messagebox.showerror("错误", "输入不合理"))
        return


    if(index >= len(action_key_list) - 1):
        root.after(0, lambda: messagebox.showerror("错误", "插值失败,当前帧数不足"))
        return
    
    # 获取前后两个关键帧之间的数据
    left_key = action_key_list[index]
    right_key = action_key_list[index + 1]

    # 生成角度插值
    linear_angles = numpy.linspace(left_key.angles, right_key.angles, num=num + 2)
    linear_angles = linear_angles[::-1]
    # 角度取整
    linear_angles = numpy.round(linear_angles)

    # 时间取平均值
    average = int(left_key.time / (num + 1) + 0.5)

    action_key_list[index].time = average
    for i in range(num):
        new_action_key = modules.action_key(linear_angles[i + 1], average)
        action_key_list.insert(index + 1, new_action_key)

    # 输出提示信息
    root.after(0, lambda: messagebox.showinfo("提示", f"插值成功,共插{num}帧"))

    # 更新action_key_listbox
    update_action_key_listbox(action_key_list)
    
action_display_index = 0
def action_key_play():
    if is_stop_action_key_display:

        return

    # 将页面上 元素设置为action_display_index
    global action_display_index
    servo_angles = action_key_list[action_display_index].angles
    update_servo_angles(servo_angles)
    current_action_key_time.set(action_key_list[action_display_index].time)

    current_select = action_display_index
    action_display_text.set(f"当前选中目标帧:{current_select + 1}")
    play_action_info.set(f"正在播放第{current_select+1}")


    root.after(action_key_list[action_display_index].time, action_key_play)

    if action_display_index == len(action_key_list) - 1:
        action_display_index = 0
    else:
        action_display_index += 1

    # 更新提示信息

def create_window():
    """串口管理设置区"""
    #将串口区域添加到一个labelFrame中
    port_frame = tk.LabelFrame(root, text="串口管理")
    port_frame.grid(row=0, column=0, padx=10, pady=10, sticky="new")

    # 刷新串口按钮
    refresh_port_button = tk.Button(port_frame, text="刷新串口")
    refresh_port_button.grid(row=1, column=1, padx=5, pady=5)
    refresh_port_button.bind("<Button-1>", button_message)

    # 串口选择下拉框
    global serial_port_list
    serial_port_list = my_serial.get_port_list()

    global port_select_combobox
    port_select_combobox = ttk.Combobox(port_frame, values=serial_port_list)
    port_select_combobox.grid(row=1, column=2, padx=5, pady=5)

    # 打开串口
    open_port_button = tk.Button(port_frame, text="打开串口")
    open_port_button.grid(row=1, column=3, padx=5, pady=5)
    open_port_button.bind("<Button-1>", button_message)

    # 启动发送数据与关闭发送数据
    send_checkbutton = tk.Checkbutton(port_frame, variable=start_send_flag, text="启动发送数据")
    send_checkbutton.grid(row=2, column=1, padx=5, pady=5)

    """动作帧管理器"""
    action_frame = tk.LabelFrame(root, text="动作帧管理")
    action_frame.grid(row=1, column=0, padx=10, pady=10, sticky="new")
    
    
    global action_list_box
    action_list_box = tk.Listbox(action_frame, selectmode="single")
    action_list_box.grid(row=0, column=0, padx=5, pady=5, rowspan=8, sticky="nsew")
    action_list_box.bind("<<ListboxSelect>>", lambda e: action_key_select(e))

    action_list_scrollbar = tk.Scrollbar(action_frame)
    action_list_scrollbar.grid(row=0, column=1, padx=5, pady=5, rowspan=8, sticky="ns")

    action_list_box.config(yscrollcommand=action_list_scrollbar.set)
    action_list_scrollbar.config(command=action_list_box.yview)

    global action_key_list
    action_key_list = fileloader.get_action_keys()
    if action_key_list is None:
        action_key_list = []
        action_key_list.append(modules.action_key(numpy.zeros(modules.servos.__len__()), 1000))

    for index, _ in enumerate(action_key_list):
        action_list_box.insert(index, str(index + 1))


    # 添加动作按钮
    add_action_button = tk.Button(action_frame, text="插入动作")
    add_action_button.grid(row=0, column=2, padx=5, pady=5, sticky="ew")
    add_action_button.bind("<Button-1>", button_message)
    
    # 保存动作帧
    add_action_button = tk.Button(action_frame, text="保存动作帧")
    add_action_button.grid(row=1, column=2, padx=5, pady=5, sticky="ew")
    add_action_button.bind("<Button-1>", button_message)

    # 删除动作按钮
    delete_action_button = tk.Button(action_frame, text="删除动作")
    delete_action_button.grid(row=2, column=2, padx=5, pady=5, sticky="ew")
    delete_action_button.bind("<Button-1>", button_message)

    # 从文件加载动作帧按钮
    load_action_button = tk.Button(action_frame, text="动作管理", command=open_action_manager_page)
    load_action_button.grid(row=4, column=2, padx=5, pady=5, sticky="ew")

    # 线性插帧
    linear_insert_button = tk.Button(action_frame, text="插入线性动作", command=open_linear_interpolation_page)
    linear_insert_button.grid(row=5, column=2, padx=5, pady=5, sticky="ew")

    # 播放动作帧
    play_action_button = tk.Button(action_frame, text="开始播放", command=open_play_action_page)
    play_action_button.grid(row=6, column=2, padx=5, pady=5, sticky="nsew")
    

    # 将帧信息复制到剪贴板
    copy_to_clipboard_button = tk.Button(action_frame, text="复制到剪贴板")
    copy_to_clipboard_button.grid(row=7, column=2, padx=5, pady=5, sticky="nsew")
    copy_to_clipboard_button.bind("<Button-1>", button_message)

    # 提示当前选中的动作帧

    global action_display_text
    action_display_text = tk.StringVar()
    action_display_text.set("当前选中目标帧:NULL")

    action_display_label = tk.Label(action_frame, textvariable=action_display_text, width=18)
    action_display_label.grid(row=8, column=2, padx=5, pady=5, sticky="nsew")
   

    """舵机管理界面"""
    servo_frame = tk.LabelFrame(root, text="舵机管理")
    servo_frame.grid(row=0, column=1, padx=10, pady=10, sticky="nsew", rowspan=2)


    servo_row = 0
    servo_column = 0

    for servo in modules.servos:
        servo_angle_scale = tk.Scale(servo_frame, 
                                    from_=0, 
                                    to=270, 
                                    length=400, 
                                    sliderlength=20, 
                                    orient=tk.HORIZONTAL, 
                                    label=servo.name,
                                    #command=lambda angle: print(angle),
                                    )
        
        servo_angle_scale.grid(row=servo_row, column=servo_column, padx=10, pady=10, sticky="nsew")
        servo_scales.append(servo_angle_scale)

        servo_row += 1
        
        servo_max_row = fileloader.get_config()['servo_list_colum']

        if servo_row == servo_max_row:
            servo_row = 0
            servo_column += 1

    global current_action_key_time
    current_action_key_time = tk.IntVar()
    current_action_key_time.set("0")
    current_action_key_time_label = tk.Label(servo_frame, text="帧时长:",)
    
    current_action_key_time_spinbox = tk.Spinbox(servo_frame, 
                                                  from_=0, 
                                                  to=10000, 
                                                  increment=1, 
                                                  textvariable=current_action_key_time,
                                                  width=5,
                                                  )
    

    if servo_column < 3:
        current_action_key_time_label.grid(row=servo_row, column=servo_column)
        current_action_key_time_spinbox.grid(row=servo_row, column=servo_column+1)
    else:
        current_action_key_time_label.grid(row=servo_max_row+1, column=0)
        current_action_key_time_spinbox.grid(row=servo_max_row+1, column=1)

    update_ui_angles()
    root.mainloop()

    fileloader.action_key_set_to_default(action_key_list)

# 打开线性插帧子页面
def open_linear_interpolation_page():
    if current_select is None:
            root.after(0, lambda: messagebox.showerror("错误", "请先选择一个动作帧"))
            return

    global linear_interpolation_page
    linear_interpolation_page = tk.Toplevel(root)
    linear_interpolation_page.title("线性插帧")
    #linear_interpolation_page.geometry("400x400")
    linear_interpolation_page.resizable(False, False)
    linear_interpolation_page.grab_set()


    target_select = current_select

    if target_select is None:
        root.after(0, lambda: messagebox.showerror("错误", "请先选择一个动作帧"))
        return

    key_index_label = tk.Label(linear_interpolation_page,
                                text=f"正在向第{target_select+1}插入动作帧")
    key_index_label.grid(row=0, column=0)

    
    key_num_label = tk.Label(linear_interpolation_page, text="键值数量:")
    key_num_label.grid(row=1, column=0)
    key_num_spinbox = tk.Spinbox(linear_interpolation_page, 
                              from_=1, 
                              to=10000,
                              increment=1,
                              width=5)
    key_num_spinbox.grid(row=1, column=1)

    # 线性插帧按钮
    linear_interpolation_button = tk.Button(linear_interpolation_page, text="线性插帧")
    linear_interpolation_button.grid(row=2, column=0, columnspan=2)
    linear_interpolation_button.bind("<Button-1>", lambda _: linear_interpolation(target_select, key_num_spinbox.get()))

    # 创建线性插帧子页面
    linear_interpolation_page.mainloop()

play_action_page:tk.Toplevel
def stop_display():
    global is_stop_action_key_display
    is_stop_action_key_display = True
    play_action_page.destroy()

def on_open_play_action_page_close():
    stop_display() 
    play_action_page.destroy()

def open_play_action_page():
    global is_stop_action_key_display

    if action_key_list.__len__() <= 1:
        root.after(0, lambda: messagebox.showerror("错误", "动作帧不足"))
        return

    global play_action_page
    play_action_page = tk.Toplevel(root)
    play_action_page.grab_set()
    play_action_page.title("播放动作")
    #play_action_page.geometry("400x400")
    play_action_page.resizable(0, 0)

    global play_action_info
    play_action_info = tk.StringVar()

    play_action_info_label = tk.Label(play_action_page, textvariable=play_action_info, width=30)
    play_action_info_label.pack()

    is_stop_action_key_display = False
    action_key_play()

    stop_play_action_button = tk.Button(play_action_page, text="停止播放", command= stop_display)
    stop_play_action_button.pack()
    play_action_page.protocol("WM_DELETE_WINDOW", on_open_play_action_page_close)
    play_action_page.mainloop()

def open_action_manager_page():
    global action_manager_page 
    action_manager_page = tk.Toplevel(root)
    action_manager_page.title("动作管理")

    global action_name_combobox

    action_name_label = tk.Label(action_manager_page, text="动作名称:")
    action_name_label.grid(row=0, column=0)
    action_name_combobox = ttk.Combobox(action_manager_page, values=fileloader.get_action_names())
    action_name_combobox.grid(row=0, column=1)

    # 保存 加载 删除
    action_manager_button_frame = tk.Frame(action_manager_page)
    action_manager_button_frame.grid(row=1, column=0, columnspan=2)
    action_manager_save_button = tk.Button(action_manager_button_frame, text="保存")
    action_manager_save_button.bind("<Button-1>", action_manager_button_message)
    action_manager_save_button.grid(row=0, column=0)
    action_manager_load_button = tk.Button(action_manager_button_frame, text="加载")
    action_manager_load_button.bind("<Button-1>", action_manager_button_message)
    action_manager_load_button.grid(row=0, column=1)
    action_manager_delete_button = tk.Button(action_manager_button_frame, text="删除")
    action_manager_delete_button.bind("<Button-1>", action_manager_button_message)
    action_manager_delete_button.grid(row=0, column=2)

def action_manager_button_message(e):
    global action_key_list

    if(action_name_combobox.get() == ""):
        root.after(0, lambda: messagebox.showerror("错误", "输入一个动作"))

    match e.widget.cget("text"):
        case "保存":
            fileloader.action_add(action_name_combobox.get(), action_key_list)
            action_manager_page.destroy()
        case "加载":
            new_action_keys = fileloader.action_load(action_name_combobox.get())
            if new_action_keys is None:
                return
            
            root.title(f"ServoUpper-{action_name_combobox.get()}")
            global current_select
            current_select = None
            action_display_text.set("当前选中目标帧:NULL")

            action_key_list.clear()
            for action_key in new_action_keys:
                action_key_list.append(action_key)

            update_action_key_listbox(action_key_list)

            action_manager_page.destroy()
        case "删除":
            fileloader.action_delete(action_name_combobox.get())
            action_manager_page.destroy()

# 创建一个更新UI中角度信息的任务
def update_ui_angles():
    global ui_servo_angles
    ui_servo_angles.clear()
    for servo_scale in servo_scales:
        ui_servo_angles.append(servo_scale.get())
    # print(ui_servo_angles)


    if start_send_flag.get():
        send_serial_data()

    root.after(10, update_ui_angles)